feat(connector): add Ilixium routing via unified connector service - #13708
Merged
Conversation
Registers Ilixium as a Hyperswitch connector so card payments can be routed to the Unified Connector Service (UCS), where the actual Ilixium Direct API integration lives. - Add `Ilixium` to `common_enums::Connector`, `euclid` `Connector` / `RoutableConnectors` and the connector -> routable-connector mappings - Add a UCS-only connector stub (`hyperswitch_connectors::connectors::Ilixium`) so `ConnectorData`/feature-matrix resolution succeeds; the stub only validates the `SignatureKey` auth shape - Add `ConnectorSpecificConfig::Ilixium` so the `x-connector-config` header sent to UCS carries the three credentials: api_key = Digest Calculation Password, key1 = MerchantId, api_secret = AccountId - Add `ilixium` to `ucs_only_connectors` in development / example / env_specific / integration_test configs so it always takes the UCS path - Add `ilixium.base_url` and dashboard connector config entries Scope is card one-time Authorize (3DS + no-3DS) only. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ector_order_id via UCS Refunds routed through the Unified Connector Service never carried `connector_order_id`: both `PaymentServiceRefundRequest` (Refund) and `RefundServiceGetRequest` (RSync) hardcoded it to `None`. The proto defines that field as "the connector-side identifier for the original payment that this refund targets", and the only site that ever populated it was CompleteAuthorize. Connectors that bind a refund to the merchant-side reference of the original payment — rather than to a gateway-generated id — therefore cannot refund at all. Ilixium is one: `POST /direct/refund`, and the `POST /history/operations` filter that RSync uses, are both keyed on the original authorisation's `transaction.merchantRef`, and no gateway-id lookup is published. UCS's `connector_transaction_id` holds Ilixium's `gatewayRef` (a dashed UUID), which is not a `merchantRef` and cannot be converted into one, so the connector refuses locally with `Missing required field: connector_order_id`, surfaced to the merchant as `IR_06`. Follow the precedent already set by the Capture and Void paths, which send the original attempt's stored `connector_request_reference_id` as `merchant_capture_id` / `merchant_void_id`: - add `RefundsData::payment_connector_request_reference_id`, populated in both the v1 and v2 `construct_refund_router_data` from `payment_attempt.connector_request_reference_id` — the same value the Authorize went out with, since `get_connector_request_reference_id` short-circuits to the stored value for every subsequent leg of a payment; - send it as `connector_order_id` on the UCS Refund and RSync requests; - relay refunds have no locally recorded payment attempt, so they pass `None`, which is what they sent before. This only populates a field that was previously always `None`, so no other connector's behaviour changes: on the refund and refund-sync path `connector_order_id` is read by Ilixium alone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
14 tasks
Ilixium performs 3-D Secure inside its own authorisation call rather than through
standalone authentication endpoints, so the two legs are POST /direct/auth and
POST /direct/threedcomplete. This opts the connector into `pre_authentication_step`
for 3DS card payments; the second leg continues to run as Authorize via
CompleteAuthorize, because /direct/threedcomplete is what settles the payment.
Note /direct/auth is terminal in two of its three outcomes — Ilixium decides at
response time whether to challenge, so a not-enrolled or frictionless card is
charged by that single call. No `Connector::Ilixium` arm is added to
`pre_authentication_step`'s `should_continue` match on purpose: the `_ => false`
default is exactly right here, and returning true would re-send /direct/auth and
earn response code 102, "Duplicate Merchant Ref".
Also fills two fields on the PreAuthenticate request that the Authorize builder
already sends, so a connector whose PreAuthenticate leg carries a full
authorisation sees the same identity and reference:
* `customer.id` from the router data's customer_id (was hardcoded None)
* `merchant_transaction_id` from connector_request_reference_id (was None);
UCS resolves this leg's reference from merchant_order_id, so this is for
connectors that read merchant_transaction_id directly, e.g. Kount's DDC
sessionId.
Verified end to end against a mock Ilixium (curl -> hyperswitch -> UCS -> mock):
non-3DS, frictionless, not-enrolled, challenge and decline all behave, and the
challenge completes to `charged` with no duplicate /direct/auth.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both were added in 2783e37 as "parity with the Authorize builder", but neither reaches anything on the Ilixium path: * `customer.id` — UCS's PreAuthenticate conversion resolves PaymentFlowData's customer_id from `customer.connector_customer_id`, not `customer.id`, and its PaymentsPreAuthenticateData conversion reads only `customer.email`. The field being populated here is not the field being read there. Ilixium also has no CreateConnectorCustomer flow, so connector_customer is always None for it. * `merchant_transaction_id` — UCS resolves this leg's connector_request_reference_id from `merchant_order_id`, which is already sent. Ilixium never reads PaymentsPreAuthenticateData::merchant_transaction_id. Faithfully mirroring Authorize is not a good enough reason to change a request builder shared by every connector. `is_pre_authentication_flow_required` is the actual Ilixium change and is untouched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ilixium's 3DS challenge could never complete. The ACS post-back to /redirect/complete/ilixium returned 400 and the attempt stayed stuck at `authentication_pending`, with UCS answering "Missing required field: payment_method". The cause is that `should_store_payment_method_data_in_vault` returns false for any connector absent from `temp_locker_enable_config`. With no entry, no payment_token is created at confirm, so `make_pm_data` has nothing to re-fetch on the CompleteAuthorize leg and hyperswitch sends no payment_method at all. This is not specific to Ilixium — it is how the fleet handles redirect 3DS. Every card connector that does it is already listed: nuvei, shift4, bluesnap, bankofamerica, cybersource, barclaycard, nmi, payme, paybox, nexixpay, redsys, worldpayxml. Ilixium was simply missing. Added to all six config files, matching each one's existing style (inline table in development/docker_compose/config.example, dotted key in the deployments). Verified end to end against a mock Ilixium: payment_token is now populated at confirm, the ACS post-back returns 302, and the attempt settles to `charged` via /direct/threedcomplete, with one /direct/auth and no duplicates. This is what makes reverting the shared-enum `NoInstrumentAfterRedirect` change possible — see the corresponding revert in the connector-service repo. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ilixium requires `customer.firstName` and `customer.surname` on every authorisation — both appear in all four Direct API customer examples and have their own validation codes (51, 52). The Optional Address feature, the only relaxation the vendor documents, explicitly relaxes addressLine1, city and postcode only; names are untouched. Hyperswitch was asking the merchant for neither, so the payment reached UCS and failed there with MissingRequiredField instead of being caught up front. This matters more since the 3DS first leg moved to PreAuthenticate. On the Authorize path the connector can fall back to splitting `customer_name` on the first whitespace, which masks the gap. `PaymentsPreAuthenticateData` has no `customer_name` field at all, so on a 3DS payment the billing address is the only name source and there is nothing to fall back on. Uses the same idiom as Deutschebank, which declares these alongside card fields and email in the same non_mandate bucket. Preferred over the no-argument BillingUserFirstName/BillingUserLastName pair that the adjacent Givepayments entry uses, because those hardcode a "card_holder_name" display name — wrong here, since Ilixium wants the customer's name, not the name on the card. Verified against a running stack: GET /account/payment_methods now advertises billing.address.first_name and billing.address.last_name for Ilixium cards, alongside the existing country and email requirements. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
deepanshu-iiitu
previously approved these changes
Aug 18, 2026
prajjwalkumar17
previously approved these changes
Aug 18, 2026
Resolves the collisions from Citigate landing on main while Ilixium was in
review. Both connectors are kept everywhere:
* ucs_only_connectors (4 config files) — took main's list and appended ilixium,
so any other edits main made to that list survive rather than being replaced
by our older copy.
* crates/connector_configs/toml/{development,production,sandbox}.toml — both
[citigate] and [ilixium] tables retained; each side's hunk was a complete
table appended at EOF.
* crates/router/src/connector.rs — the two sides were byte-identical apart from
each side's own connector, so main's list was taken and ilixium inserted in
alphabetical position after iatapay.
Re-wrapped with nightly rustfmt: inserting into that `pub use` list left a
126-char line that stable rustfmt cannot re-flow (the repo's
imports_granularity/group_imports settings are nightly-only), which would have
failed `cargo +nightly fmt --all --check` in CI.
Verified both sides survived — citigate and ilixium occurrence counts in every
resolved file match main and the branch respectively — and cargo check is clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
shuklatushar226
dismissed stale reviews from prajjwalkumar17 and deepanshu-iiitu
via
August 18, 2026 09:30
ff40f6d
prajjwalkumar17
previously approved these changes
Aug 18, 2026
Sakilmostak
reviewed
Aug 18, 2026
| @@ -0,0 +1,2 @@ | |||
| request-property-one-of-removed warn | |||
| response-property-one-of-added warn | |||
Contributor
There was a problem hiding this comment.
is this change needed?
Swept into the main->feat/grace-ilixium merge from an untracked working-tree file; present in neither parent. The workflow dropped --severity-levels in bc75604, so nothing reads it, and this branch's only spec delta is an enum value already covered by .oasdiff-warn-ignore.yaml. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sakilmostak
approved these changes
Aug 18, 2026
deepanshu-iiitu
approved these changes
Aug 18, 2026
sai-harsha-vardhan
approved these changes
Aug 19, 2026
AmitsinghTanwar007
approved these changes
Aug 19, 2026
shuklatushar226
enabled auto-merge
August 19, 2026 08:06
shuklatushar226
disabled auto-merge
August 19, 2026 08:08
shuklatushar226
enabled auto-merge
August 19, 2026 08:08
jagan-jaya
approved these changes
Aug 19, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Type of Change
Description
Registers Ilixium as a Hyperswitch connector so that card payments can be routed to the Unified Connector Service (UCS), where the actual Ilixium Direct API integration lives. There is no in-repo Ilixium HTTP integration — Ilixium is added to
ucs_only_connectors, so every attempt takes the UCS gateway path.Linked UCS PR: juspay/hyperswitch-prism#2125
What this does
Adds
Ilixiumtocommon_enums::Connector,euclid::enums::{Connector, RoutableConnectors}and theConnector -> RoutableConnectorsmappings, soConnector::from_str("ilixium")(used byshould_call_unified_connector_service) resolves and the connector is routable.Adds a UCS-only connector stub at
crates/hyperswitch_connectors/src/connectors/ilixium.rs(connector-template shape). It exists only soConnectorData::get_connector_by_name/ feature-matrix resolution succeed before the UCS/Direct decision is made; its only meaningful logic is validating theSignatureKeyauth shape.Adds
ConnectorSpecificConfig::Ilixiumincrates/router/src/core/unified_connector_service/connector_config.rs. Without this,build_connector_config_headerreturns an error and the UCS call fails. Thex-connector-configheader now carries:api_keyx-merchant-digest(never transmitted)key1merchant.merchantIdapi_secretmerchant.accountIdAdds
ilixiumtoucs_only_connectorsinconfig/development.toml,config/config.example.toml,config/deployments/env_specific.tomlandconfig/deployments/integration_test.toml(deliberately not sandbox/production — no credentials yet).Adds
ilixium.base_url = "https://prprocessing.ilixium.com/platform/ili"to the config files, control-center connector config entries (SignatureKey, credit/debit card networks) and a cards required-fields entry (card fields + email + billing country, which the UCS connector mandates).Regenerates the
ilixiumenum value into the OpenAPI specs.Additional fix:
connector_order_idon UCS refund / refund-sync requestsRefunds routed through UCS never carried
connector_order_id— bothPaymentServiceRefundRequestandRefundServiceGetRequesthardcoded it toNoneincrates/router/src/core/unified_connector_service/transformers.rs. Per the proto, that field is"the connector-side identifier for the original payment that this refund targets".
This is a real, connector-agnostic gap, and it makes refunds impossible for connectors that bind a
refund to the merchant-side reference of the original payment rather than to a gateway-generated
id. Ilixium is one:
POST /direct/refundand thePOST /history/operationsfilter used by RSyncare both keyed on the original authorisation's
transaction.merchantRef, and Ilixium publishes nogateway-id lookup. UCS's
connector_transaction_idholds Ilixium'sgatewayRef(a dashed UUID),which is not a
merchantRefand cannot be converted into one, so the connector refuses locallywith
Missing required field: connector_order_id(surfaced asIR_06) rather than sending areference that points at nothing.
The fix follows the precedent already established by the Capture and Void paths, which send the
original attempt's stored
connector_request_reference_idasmerchant_capture_id/merchant_void_id:RefundsData::payment_connector_request_reference_id, populated in both v1 and v2construct_refund_router_datafrompayment_attempt.connector_request_reference_id(the samevalue the Authorize went out with;
core::utils::get_connector_request_reference_idshort-circuits to the stored value so every leg of a payment reuses it).
connector_order_idon both the UCS Refund and RSync requests.None— unchangedbehaviour.
This only populates a field that was previously always
None, so no other connector's behaviourchanges:
connector_order_idis read on the refund/refund-sync path by Ilixium alone (the otherUCS connectors that use it — Razorpay, Easebuzz, PayPal, Cashfree, Airwallex, Affirm, Flywire —
read it on Authorize/CreateOrder, which this PR does not touch).
Scope
Card one-time payments (3DS + no-3DS) plus the Capture / Void / Refund / RSync legs that UCS already implements. No mandates or wallets.
Motivation and Context
Ilixium was implemented in the connector-service (UCS) repo. Hyperswitch could not route to it because the connector name did not exist in the
Connectorenum and there was noConnectorSpecificConfigvariant to build thex-connector-configheader from.How did you test it?
cargo check --workspacewith the full v1 feature set — cleancargo check --workspacewith the full v2 feature set — cleancargo clippy --workspace --all-targets(v1 features) — cleancargo +nightly fmt --all -- --check— cleanConnectorConfig::get_connector_config(Connector::Ilixium)loads the newSignatureKeydashboard configEnd-to-end verified against a mock Ilixium server (no Ilixium sandbox credentials exist yet),
driving Hyperswitch -> UCS -> mock and asserting on the bytes the mock actually received:
POST /refunds-> refund sync, all succeeded.POST /platform/ili/direct/refund, and itstransaction.merchantRefisbyte-identical to the
merchantRefsent on the originalPOST /platform/ili/direct/auth— whichis the whole point of the fix. Before the fix the same flow failed with
IR_06 Missing required field: connector_order_id.POST /platform/ili/history/operationsand the refund resolved tosucceeded.x-merchant-digestrecomputes correctly from the raw received bytes on every request.succeeded, no regression.End-to-end testing is planned against a mock Ilixium server, because no Ilixium sandbox credentials exist yet. Manual verification recipe:
ucs_enabled = "true"into theconfigstable, and point[grpc_client.unified_connector_service] base_urlat a running connector-service.POST /account/{merchant_id}/connectors { "connector_type": "payment_processor", "connector_name": "ilixium", "connector_account_details": { "auth_type": "SignatureKey", "api_key": "<Digest Calculation Password>", "key1": "<MerchantId>", "api_secret":"<AccountId>" }, "payment_methods_enabled": [ { "payment_method": "card", "payment_method_types": [ { "payment_method_type": "credit", "card_networks": ["Visa","Mastercard"], "minimum_amount": 1, "maximum_amount": 68607706, "recurring_enabled": false, "installment_payment_enabled": false }, { "payment_method_type": "debit", "card_networks": ["Visa","Mastercard"], "minimum_amount": 1, "maximum_amount": 68607706, "recurring_enabled": false, "installment_payment_enabled": false } ] } ] }"connector": ["ilixium"], card payment method data,email,billing.address.country, and — for 3DS —authentication_type: "three_ds", areturn_urland a fullbrowser_infoblock.Checklist
cargo +nightly fmt --allcargo clippy🤖 Generated with Claude Code